class Region:
    # info:为地区属性的字典，
    #      一个Region对象的所有数据成员构成的dict，
    #      与init.json中的regions数组的一个元素格式完全相同。
    # __init__:检查除_model_param以外的元素取值是否合法
    def __init__(self, info):
        # 地名_name
        self._name = info['name']

        # ***以下所有传入参数:
        # ***在边界内:直接赋值
        # ***为空、不在边界内、错误类型:取边界值

        # 易感者_susceptible
        try:
            if info['susceptible'] >= 0:
                self._susceptible = info['susceptible']
            else:
                self._susceptible = 0
        except TypeError:
            self._susceptible = 0

        # 潜伏者_exposed
        try:
            if info['exposed'] >= 0:
                self._exposed = info['exposed']
            else:
                self._exposed = 0
        except TypeError:
            self._exposed = 0

        # 感染者_infected
        try:
            if info['infected'] >= 0:
                self._infected = info['infected']
            else:
                self._infected = 0
        except TypeError:
            self._infected = 0

        # 治愈者_recovered
        try:
            if info['recovered'] >= 0:
                self._recovered = info['recovered']
            else:
                self._recovered = 0
        except TypeError:
            self._recovered = 0

        # 死亡者_dead
        try:
            if info['dead'] >= 0:
                self._dead = info['dead']
            else:
                self._dead = 0
        except TypeError:
            self._dead = 0

        # 迁出率_export_rate:-1.00~1.00
        try:
            if -1 <= info['export_rate'] <= 1:
                self._export_rate = info['export_rate']
            elif info['export_rate'] > 1:
                self._export_rate = 1
            else:
                self._export_rate = -1
        except TypeError:
            self._export_rate = 0

        # _dest_pref:以地名为引索的字典，值为迁出偏好，0.00~1.00
        #            与init.json中的dest_pref完全相同
        total_pref = 0
        valid = True
        # 对每项pref检查并求和
        for each_pref in info["dest_pref"].values():
            try:
                # 范围错误，不更新dest_pref
                if each_pref < 0:
                    valid = False
                    break
                else:
                    total_pref += each_pref
            # 类型错误，不更新dest_pref
            except TypeError:
                valid = False
                break
        # 和错误，不更新dest_pref
        if total_pref != 1:
            valid = False
        # 更新
        if valid:
            self._dest_pref = info['dest_pref']
        # 不合法时用均值代替更新
        else:
            self._dest_pref = {}
            avg_pref = 1 / len(info['dest_pref'].keys())
            for each_key in info['dest_pref'].keys():
                self._dest_pref.update({each_key: avg_pref})

        # _model_param:传播模型的参数字典
        #              与init.json中的model_param完全相同
        #              合法性由用户自定义函数校验
        self._model_param = info['model_param']

    # info:Region类的任意数据成员与type（标记覆盖模式）所构成的字典。
    #      即updates.json文件中的单个地区的数据。
    # update:使用info更新数据成员
    #        info中的项type=0时，覆盖更新；type=1时，增量更新（如果缺省type，则默认type=0）。
    #        info为地区属性的字典，且该字典中的所有项都可以缺省（更新时遍历info即可）。
    #        同时，需要检查除_model_param以外的元素取值是否合法
    def update(self, update_info):
        # type缺省时默认为0
        update_type = update_info.get("type", 0)
        if update_type == 0:
            # 覆盖更新，若某项为空则该项不更新
            # ***以下所有传入参数:
            # ***在边界内:直接覆盖更新
            # ***不在边界内且不为空、错误类型:取边界值
            # ***为空:不更新
            if update_info.get('susceptible') is not None:
                try:
                    if update_info['susceptible'] >= 0:
                        self._susceptible = update_info['susceptible']
                    else:
                        self._susceptible = 0
                except TypeError:
                    self._susceptible = 0

            if update_info.get('exposed') is not None:
                try:
                    if update_info['exposed'] >= 0:
                        self._exposed = update_info['exposed']
                    else:
                        self._exposed = 0
                except TypeError:
                    self._exposed = 0

            if update_info.get('infected') is not None:
                try:
                    if update_info['infected'] >= 0:
                        self._infected = update_info['infected']
                    else:
                        self._infected = 0
                except TypeError:
                    self._infected = 0

            if update_info.get('recovered') is not None:
                try:
                    if update_info['recovered'] >= 0:
                        self._recovered = update_info['recovered']
                    else:
                        self._recovered = 0
                except TypeError:
                    self._recovered = 0

            if update_info.get('dead') is not None:
                try:
                    if update_info['dead'] >= 0:
                        self._dead = update_info['dead']
                    else:
                        self._dead = 0
                except TypeError:
                    self._dead = 0

            if update_info.get('export_rate') is not None:
                try:
                    if -1 <= update_info['export_rate'] <= 1:
                        self._export_rate = update_info['export_rate']
                    elif update_info['export_rate'] > 1:
                        self._export_rate = 1
                    else:
                        self._export_rate = -1
                except TypeError:
                    self._export_rate = 0

            # 部分更新字典
            if update_info.get("dest_pref") is not None:
                total_pref = 0
                valid = True
                # 对每项pref检查并求和
                for each_pref in update_info["dest_pref"].values():
                    try:
                        # 范围错误，不更新dest_pref
                        if each_pref < 0:
                            valid = False
                            break
                        else:
                            total_pref += each_pref
                    # 类型错误，不更新dest_pref
                    except TypeError:
                        valid = False
                        break
                # 和错误，不更新dest_pref
                if total_pref != 1:
                    valid = False
                # 更新
                if valid:
                    self._dest_pref = update_info['dest_pref']

            # 部分更新字典
            if update_info.get("model_param") is not None:
                self._model_param.update(update_info["model_param"])

        elif update_type == 1:
            # 增量更新，若某项为空则该项不更新
            # ***以下所有传入参数:
            # ***不管在或不在边界内:都增量(减量)更新，但需要检查结果是否在边界内，不在边界内则取边界值
            # ***为空、错误类型:不更新

            if update_info.get('susceptible') is not None:
                try:
                    self._susceptible += update_info['susceptible']
                    if self._susceptible < 0:
                        self._susceptible = 0
                except TypeError:
                    pass

            if update_info.get('exposed') is not None:
                try:
                    self._exposed += update_info['exposed']
                    if self._exposed < 0:
                        self._exposed = 0
                except TypeError:
                    pass

            if update_info.get('infected') is not None:
                try:
                    self._infected += update_info['infected']
                    if self._infected < 0:
                        self._infected = 0
                except TypeError:
                    pass

            if update_info.get('recovered') is not None:
                try:
                    self._recovered += update_info['recovered']
                    if self._recovered < 0:
                        self._recovered = 0
                except TypeError:
                    pass

            if update_info.get('dead') is not None:
                try:
                    self._dead += update_info['dead']
                    if self._dead < 0:
                        self._dead = 0
                except TypeError:
                    pass

            if update_info.get('export_rate') is not None:
                try:
                    self._export_rate += update_info['export_rate']
                    if self._export_rate > 1:
                        self._export_rate = 1
                    elif self._export_rate < -1:
                        self._export_rate = -1
                except TypeError:
                    pass

            # 部分更新字典
            if update_info.get("dest_pref") is not None:
                total_pref = 0
                valid = True
                # 对每项pref检查并求和
                for each_pref in update_info["dest_pref"].values():
                    try:
                        # 范围错误，不更新dest_pref
                        if each_pref < 0:
                            valid = False
                            break
                        else:
                            total_pref += each_pref
                    # 类型错误，不更新dest_pref
                    except TypeError:
                        valid = False
                        break
                # 和错误，不更新dest_pref
                if total_pref != 1:
                    valid = False
                # 更新
                if valid:
                    self._dest_pref = update_info['dest_pref']

            # 部分更新字典
            if update_info.get("model_param") is not None:
                for key, data in update_info['model_param']:
                    # 只增量更新存在的数据
                    if self._model_param.get(key) is not None:
                        self._model_param[key] += data
        else:
            raise ValueError

    # get_migration:通过export_rate与dest_pref计算本地向其他地区的迁出人口组成
    # 返回值数组:每个元素为人口组成字典，且更新模式type=1。
    #           需要注意，name参数为人口迁出的目的地。
    #           当前地点的人口变化也需要包含在返回值数组内（即需要计算当地的人口减少）。
    #           {"name": "destination", "type": 1, "susceptible": 10,
    #            "exposed": 0, "infected": 0, "recovered": 0}
    #           如上结构构成的list
    def get_migration(self):
        sus = self._susceptible * self._export_rate
        exp = self._exposed * self._export_rate
        inf = self._infected * self._export_rate
        rec = self._recovered * self._export_rate
        temp_list = [{'name': self._name, 'type': 1, 'susceptible': -sus, 'exposed': -exp,
                      'infected': -inf, 'recovered': -rec}]
        for region, pref in self._dest_pref.items():
            temp_dict = {'name': region, 'type': 1, 'susceptible': sus * pref, 'exposed': exp * pref,
                         'infected': inf * pref, 'recovered': rec * pref}
            temp_list.append(temp_dict)
        return temp_list

    # get_population:返回人口组成字典
    # 返回值:{"susceptible": 10, "exposed": 0, "infected": 0,
    #        "recovered": 0, "dead": 0}
    def get_population(self):
        return {'susceptible': self._susceptible, 'exposed': self._exposed, 'infected': self._infected,
                'recovered': self._recovered, 'dead': self._dead}

    # get_param:返回_model_param
    # _model_param:与init.json中的model_param完全相同
    def get_param(self):
        return self._model_param

    # get_export_info:返回迁出率与迁出偏好组成的字典
    # 返回值:{"export_rate": 0.05, "dest_pref": {"region2": 1.00}}
    def get_export_info(self):
        return {'export_rate': self._export_rate, 'dest_pref': self._dest_pref}
